🎖️GitЯра🎖️
Commit cca7c274b98dbe224c3df6f511da7f3263c697ab
Parents : bc9f163
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-05-31T07:16:10-07:00
Committer : GitHub <noreply@github.com>
Date : 2026-05-31T14:16:10Z
fix: address top Crashlytics crashes and non-fatals for build 29320984 (#5684)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changes
3 files changed, 43 insertions(+), 3 deletions(-)
Diff
diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt b/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
index af8704d542..3efaf68ef2 100644
--- a/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
@@ -18,9 +18,12 @@ package org.meshtastic.app.map.component
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.lifecycle.compose.currentStateAsState
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
@@ -45,6 +48,7 @@ fun NodeClusterMarkers(
val view = LocalView.current
val lifecycleOwner = LocalLifecycleOwner.current
val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
+ val lifecycleState by lifecycleOwner.lifecycle.currentStateAsState()
// Workaround for https://github.com/googlemaps/android-maps-compose/issues/858
// and https://github.com/googlemaps/android-maps-compose/issues/875
@@ -66,6 +70,10 @@ fun NodeClusterMarkers(
onDispose {}
}
+ // Guard against the cluster renderer's async Handler trying to render markers
+ // after the lifecycle has stopped — the internal ComposeView requires an active lifecycle.
+ if (!lifecycleState.isAtLeast(Lifecycle.State.STARTED)) return
+
Clustering(
items = nodeClusterItems,
onClusterClick = onClusterClick,
diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt b/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt
index 9930820676..c65c0bec1d 100644
--- a/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/node/component/InlineMap.kt
@@ -17,10 +17,18 @@
package org.meshtastic.app.node.component
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.compose.LocalSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.Circle
@@ -46,6 +54,23 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
true -> ComposeMapColorScheme.DARK
else -> ComposeMapColorScheme.LIGHT
}
+
+ // Workaround for maps-compose issue where MarkerComposable's internal ComposeView
+ // cannot find ViewTreeLifecycleOwner, causing crash on bitmap rendering.
+ val view = LocalView.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
+ DisposableEffect(lifecycleOwner, savedStateRegistryOwner) {
+ val root = view.rootView
+ root.setViewTreeLifecycleOwner(lifecycleOwner)
+ root.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
+ if (view !== root) {
+ view.setViewTreeLifecycleOwner(lifecycleOwner)
+ view.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
+ }
+ onDispose {}
+ }
+
key(node.num) {
val location = LatLng(node.latitude, node.longitude)
val cameraState = rememberCameraPositionState {
@@ -79,7 +104,9 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
strokeWidth = 2f,
)
}
- MarkerComposable(state = rememberUpdatedMarkerState(position = latLng)) { NodeChip(node = node) }
+ MarkerComposable(state = rememberUpdatedMarkerState(position = latLng)) {
+ NodeChip(node = node, modifier = Modifier.defaultMinSize(minWidth = 64.dp, minHeight = 28.dp))
+ }
}
}
}
diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt b/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt
index 6884dc9e1e..97739a072d 100644
--- a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt
+++ b/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt
@@ -23,6 +23,10 @@ import com.juul.kable.logs.LogEngine
* Bridges Kable's internal logging to [Kermit][Logger] so BLE lifecycle events (connect, disconnect, subscribe, GATT
* operations) appear in the standard app logs rather than going to [System.out] via Kable's default
* [com.juul.kable.logs.SystemLogEngine].
+ *
+ * Kable logs connection failures and disconnections at error level, but these are expected BLE operational events — not
+ * application bugs. We downgrade error/assert to warn so these don't trigger non-fatal exception recording in
+ * Crashlytics (which records any Kermit Error-level log with a throwable as a non-fatal).
*/
internal object KermitLogEngine : LogEngine {
override fun verbose(throwable: Throwable?, tag: String, message: String) {
@@ -42,10 +46,11 @@ internal object KermitLogEngine : LogEngine {
}
override fun error(throwable: Throwable?, tag: String, message: String) {
- Logger.e(throwable) { "[$tag] $message" }
+ // Downgrade: Kable "errors" are operational (failed connect, disconnect requested) not app bugs.
+ Logger.w(throwable) { "[$tag] $message" }
}
override fun assert(throwable: Throwable?, tag: String, message: String) {
- Logger.e(throwable) { "[$tag] $message" }
+ Logger.w(throwable) { "[$tag] $message" }
}
}
Served by rngit 1.5.0 - Generated in 0.09s